package com.example.codeanalysis.parser;

import com.example.codeanalysis.parser.AnaResult.API;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.ImportDeclaration;
import org.eclipse.jdt.core.dom.MarkerAnnotation;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.NormalAnnotation;
import org.eclipse.jdt.core.dom.SimpleType;
import org.eclipse.jdt.core.dom.SingleMemberAnnotation;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.TypeDeclaration;

@SuppressWarnings("ALL")
@Slf4j
public class JavaSourceParser {

  private final static String[] CONTROLLER_ANNO_NAMES = {
      "Controller",
      "RestController",
      "ControllerEndpoint",
      "RestControllerEndpoint"
  };

  private final static Predicate<String> IS_CONTROLLER = (name) -> Arrays.asList(CONTROLLER_ANNO_NAMES).contains(name);

  private final File root;

  private final List<TypeResult> controllers = new ArrayList<>(16);
  /**
   * key 为类的全名称，value为TypeResult，用于查找controller的父类
   */
  private final Map<String, TypeResult> javaFilesAll = new HashMap<>(64);
  private final String sysCode;
  private final String subSysCode;

  public JavaSourceParser(String sysCode, String subSysCode, String rootPath) {
    root = new File(rootPath);
    this.sysCode = sysCode;
    this.subSysCode = subSysCode;
  }


  public List<AnaResult> parse() {
    loop(root);
    List<AnaResult> results = new ArrayList<>(controllers.size());

    controllers.forEach(tr -> {
      AnaResult result = new AnaResult();
      result.setClazz(tr.getClazz());
      // 获取类上@RequestMapping的path
      API api = getAPI(tr.getModifiers());
      String commPath = "/";
      if (api != null) {
        commPath = StringUtils.defaultString(api.getUrl(), "/");
        if (!commPath.startsWith("/")) {
          commPath = "/" + commPath;
        }
      }
      result.setCommPath(commPath);
      // 获取方法列表, 并获取所有API
      MethodDeclaration[] methods = ((TypeDeclaration) tr.getJava()).getMethods();
      for (MethodDeclaration method : methods) {
        result.add(getAPI(method.modifiers()));
      }
      // 获取父类的API
      Type superType = ((TypeDeclaration) tr.getJava()).getSuperclassType();
      if (superType != null) {
        if (superType.isSimpleType()) {
          String superName = ((SimpleType) superType).getName().getFullyQualifiedName();
          // 非全名称的从imports里面匹配
          if (!superName.contains(".")) {
            boolean found = false;
            for (ImportDeclaration im : tr.getImports()) {
              String superFullName = im.getName().getFullyQualifiedName();
              if (superFullName.endsWith(superName)) {
                superName = superFullName;
                found = true;
                break;
              }
            }
            // 若imports未找到说明父子类同包
            if (!found) {
              int index = tr.getClazz().lastIndexOf('.');
              String packageName = "";
              if (index > 0) {
                packageName = tr.getClazz().substring(0, tr.getClazz().lastIndexOf('.') + 1);
              }
              superName = packageName + superName;
            }
          }
          TypeResult superTypeResult = javaFilesAll.get(superName);
          if (superTypeResult != null) {
            MethodDeclaration[] superMethods = ((TypeDeclaration) superTypeResult.getJava()).getMethods();
            for (MethodDeclaration method : superMethods) {
              result.add(getAPI(method.modifiers()));
            }
          }
        }
      }

      results.add(result);
    });
    return results;
  }

  private API getAPI(List<ASTNode> modifiers) {
    API api = null;
    if (modifiers == null || modifiers.size() < 1) {
      return api;
    }
    for (ASTNode mod : modifiers) {
      int nodeType = mod.getNodeType();
      if (nodeType == ASTNode.NORMAL_ANNOTATION || nodeType == ASTNode.MARKER_ANNOTATION
          || nodeType == ASTNode.SINGLE_MEMBER_ANNOTATION) {
        Annotation annotation = (Annotation) mod;
        String name = annotation.getTypeName().getFullyQualifiedName();
        if (name != null && name.length() > 9 && name.endsWith("Mapping")) {
          api = new API();
          api.setMethod(name.substring(0, name.indexOf("Mapping")));
          if (mod instanceof NormalAnnotation) {
            NormalAnnotation normalAnnotation = (NormalAnnotation) mod;
            api.setUrl(normalAnnotation.values());
          } else if (mod instanceof SingleMemberAnnotation) {
            SingleMemberAnnotation singleMemberAnnotation = (SingleMemberAnnotation) mod;
            api.setUrl(singleMemberAnnotation.getValue());
          } else if (mod instanceof MarkerAnnotation) {
            api.setUrl("");
          }
        }
      }
    }
    return api;
  }

  /**
   * 循环遍历文件
   */
  private void loop(File subRoot) {
    if (subRoot.isDirectory()) {
      File[] subFiles = subRoot.listFiles(file -> file.isDirectory() || file.getName().endsWith("java"));
      if (subFiles == null || subFiles.length == 0) {
        return;
      }
      for (File file : subFiles) {
        if (file.isFile() && file.canRead()) {
          parseJava(file);
        } else if (file.isDirectory() && file.canExecute()) {
          loop(file);
        }
      }
    }
  }

  private void parseJava(File java) {
    CompilationUnit cUnit = getCompilationUnit(java);
    if (cUnit != null) {
      List<AbstractTypeDeclaration> typeList = cUnit.types();
      typeList.stream()
          .map(type -> {
            TypeResult result = getTypeResult(cUnit, type);
            javaFilesAll.put(result.getClazz(), result);
            return result;
          })
          .filter(type -> {
            List<ASTNode> modifiers = type.getModifiers();
            return modifiers.stream()
                .anyMatch(node -> {
                  int nodeType = node.getNodeType();
                  if (nodeType == ASTNode.NORMAL_ANNOTATION || nodeType == ASTNode.MARKER_ANNOTATION
                      || nodeType == ASTNode.SINGLE_MEMBER_ANNOTATION) {
                    Annotation annotation = (Annotation) node;
                    String name = annotation.getTypeName().getFullyQualifiedName();
                    return IS_CONTROLLER.test(name);
                  }
                  return false;
                });
          })
          .forEach(controllers::add);
    }
  }

  private TypeResult getTypeResult(CompilationUnit cUnit, AbstractTypeDeclaration source) {
    TypeResult result = new TypeResult();
    result.setJava(source);
    String packageName = cUnit.getPackage().getName().getFullyQualifiedName();
    String clazz = source.getName().getFullyQualifiedName();
    result.setClazz(packageName + "." + clazz);
    result.setModifiers(source.modifiers());
    result.setImports(cUnit.imports());
    return result;
  }

  private CompilationUnit getCompilationUnit(File javaFile) {
    try (InputStream ins = new FileInputStream(javaFile)) {
      String content = IOUtils.toString(ins, StandardCharsets.UTF_8);
      if (StringUtils.isNotEmpty(content)) {
        ASTParser astParser = ASTParser.newParser(AST.JLS16);
        Map<String, String> options = JavaCore.getOptions();
        JavaCore.setComplianceOptions(JavaCore.VERSION_1_8, options);
        astParser.setCompilerOptions(options);
        astParser.setKind(ASTParser.K_COMPILATION_UNIT);
        astParser.setSource(content.toCharArray());
        return (CompilationUnit) astParser.createAST(null);
      }
    } catch (IOException ioe) {
      log.warn(ioe.getMessage());
    }
    return null;
  }

}
